import { useCallback, useReducer, useState } from "react";
import { useMountedRef } from "./index";

interface State<D> {
  error: Error | null
  data: D | null
  stat: "idle" | "loading" | "error" | "success"
}

const defaultInitialState: State<null> = {
  stat: "idle",
  error: null,
  data: null
};

const defaultConfig = {
  throwOnError: false
};

const useSafeDispatch = <T>(dispatch: (...arg: T[]) => void) => {
  const mountedRef = useMountedRef()
  return useCallback((...args:  T[]) => (mountedRef.current? dispatch(...args) : void 0), [dispatch, mountedRef])
}

// TODO 用useReducer改造
export const useAsync = <D> (
  initialState?: State<D>,
  initialConfig?: typeof defaultConfig
) => {
  const [state, dispatch] = useReducer(
    (state: State<D>, action: Partial<State<D>>) => ({ ...state, ...action }), {
    ...defaultInitialState,
    ...initialState
  });

  const safeDispatch = useSafeDispatch(dispatch)

  const [retry, setRetry] = useState(() => () => {});

  const config = { ...defaultConfig, ...initialConfig };

  // 成功请求
  const setData = useCallback((data: D) => safeDispatch({
    data,
    error: null,
    stat: "success"
  }), [safeDispatch]);

  // 失败请求
  const setError = useCallback((error: Error) => safeDispatch({
    error,
    stat: "error",
    data: null
  }), [safeDispatch]);

  const run = useCallback((
    promise: Promise<D>,
    runConfig?: { retry: () => Promise<D> }
  ) => {
    if (!promise || !promise.then) {
      throw new Error("请传入 Promise 类型数据");
    }
    setRetry(() => () => {
      if (runConfig?.retry) {
        run(runConfig?.retry(), runConfig);
      }
    });

    safeDispatch({ stat: "loading" });

    return promise.then(data => {
      setData(data);
      return data;
    }).catch(error => {
      // catch 会消化异常，而不抛出异常
      setError(error);
      if (config.throwOnError) return Promise.reject(error);
      return error;
    });
  }, [config.throwOnError, safeDispatch, setData, setError]);


  return {
    isIdle: state.stat === "idle",
    isLoading: state.stat === "loading",
    isError: state.stat === "error",
    isSuccess: state.stat === "success",
    run,
    setData,
    // retry 被调用时重新跑一遍run，让state刷新一遍
    retry,
    setError,
    ...state
  };
};


// export const useAsync = <D> (initialState?: State<D>, initialConfig?: typeof defaultConfig) => {
//   const [state, setState] = useState<State<D>>({
//     ...defaultInitialState,
//     ...initialState
//   });
//
//   const mountedRef = useMountedRef();
//
//   // 1、
//   // useState直接传入函数的含义：惰性初始化; 所以，要用useState保存函数，不能直接传入函数
//   const [retry, setRetry] = useState(() => () => {
//   });
//   // 2、
//   // const retry = useRef(() => {})
//
//   const config = { ...defaultConfig, ...initialConfig };
//
//   // 成功请求
//   const setData = useCallback((data: D) => setState({
//     data,
//     error: null,
//     stat: "success"
//   }), [])
//
//   // 失败请求
//   const setError = useCallback((error: Error) => setState({
//     error,
//     stat: "error",
//     data: null
//   }), []);
//
//   const run = useCallback((
//     promise: Promise<D>,
//     runConfig?: { retry: () => Promise<D> }
//   ) => {
//     if (!promise || !promise.then) {
//       throw new Error("请传入 Promise 类型数据");
//     }
//     setRetry(() => () => {
//       if (runConfig?.retry) {
//         run(runConfig?.retry(), runConfig);
//       }
//     });
//     // retry.current = () => run(promise)
//     setState(prevState => ({ ...prevState, stat: "loading" }));
//     return promise.then(data => {
//       if (mountedRef.current) {
//         setData(data);
//       }
//       return data;
//     }).catch(error => {
//       // catch 会消化异常，而不抛出异常
//       setError(error);
//       if (config.throwOnError) return Promise.reject(error);
//       return error;
//     });
//   }, [config.throwOnError, mountedRef, setData, setError]);
//
//
//   return {
//     isIdle: state.stat === "idle",
//     isLoading: state.stat === "loading",
//     isError: state.stat === "error",
//     isSuccess: state.stat === "success",
//     run,
//     setData,
//     // retry 被调用时重新跑一遍run，让state刷新一遍
//     retry,
//     setError,
//     ...state
//   };
// };
